"""
本地存储模块
实现本地文件系统存储
"""
import os
import shutil
import tempfile
from datetime import datetime
from typing import BinaryIO, Dict, Any, List
from pathlib import Path

from flask import current_app, url_for
from werkzeug.utils import secure_filename

from app.storage.base import StorageBase
from app.utils.exceptions import StorageError


class LocalStorage(StorageBase):
    """
    本地文件系统存储实现
    """
    
    def __init__(self, config: Dict[str, Any]):
        """
        初始化本地存储
        
        Args:
            config: 存储配置
        """
        super().__init__(config)
        self.base_path = config.get('base_path', '')
        
    def setup(self):
        """设置本地存储环境"""
        if not self.base_path:
            # 使用Flask实例目录
            self.base_path = current_app.config.get('UPLOAD_FOLDER', 'instance/uploads')
        
        # 确保基础目录存在
        os.makedirs(self.base_path, exist_ok=True)
        
        # 创建子目录结构
        subdirs = ['images', 'documents', 'avatars', 'temp']
        for subdir in subdirs:
            os.makedirs(os.path.join(self.base_path, subdir), exist_ok=True)
        
        current_app.logger.info(f"本地存储初始化完成: {self.base_path}")
    
    def save(self, file_data: BinaryIO, filename: str, 
             folder: str = '', metadata: Dict[str, Any] = None) -> Dict[str, Any]:
        """
        保存文件到本地文件系统
        
        Args:
            file_data: 文件数据
            filename: 原始文件名
            folder: 存储文件夹
            metadata: 文件元数据
            
        Returns:
            dict: 文件信息
        """
        try:
            # 验证文件
            self.validate_file_size(file_data)
            self.validate_file_type(filename)
            
            # 生成安全文件名
            safe_filename = self.generate_filename(filename)
            
            # 确定存储路径
            if folder:
                storage_path = os.path.join(self.base_path, folder)
                os.makedirs(storage_path, exist_ok=True)
            else:
                storage_path = self.base_path
            
            file_path = os.path.join(storage_path, safe_filename)
            
            # 获取文件信息
            file_info = self.get_file_info(file_data, filename)
            
            # 保存文件
            with open(file_path, 'wb') as f:
                # 重置文件指针到开始位置
                file_data.seek(0)
                shutil.copyfileobj(file_data, f)
            
            # 构建返回信息
            result = {
                'file_id': safe_filename,
                'file_path': file_path,
                'filename': safe_filename,
                'original_filename': filename,
                'folder': folder,
                'url': self.get_url(safe_filename, folder),
                'size': file_info['file_size'],
                'mime_type': file_info['mime_type'],
                'extension': file_info['file_extension'],
                'created_at': datetime.utcnow().isoformat(),
                'metadata': metadata or {}
            }
            
            current_app.logger.info(f"文件保存成功: {file_path}")
            return result
            
        except Exception as e:
            current_app.logger.error(f"文件保存失败: {str(e)}")
            raise StorageError(f"文件保存失败: {str(e)}")
    
    def get(self, file_id: str) -> BinaryIO:
        """
        从本地文件系统获取文件
        
        Args:
            file_id: 文件名或相对路径
            
        Returns:
            BinaryIO: 文件二进制流
        """
        try:
            file_path = self._get_full_path(file_id)
            
            if not os.path.exists(file_path):
                raise StorageError(f"文件不存在: {file_id}")
            
            # 以二进制模式打开文件
            file_obj = open(file_path, 'rb')
            return file_obj
            
        except StorageError:
            raise
        except Exception as e:
            current_app.logger.error(f"文件获取失败: {str(e)}")
            raise StorageError(f"文件获取失败: {str(e)}")
    
    def delete(self, file_id: str) -> bool:
        """
        从本地文件系统删除文件
        
        Args:
            file_id: 文件名或相对路径
            
        Returns:
            bool: 是否删除成功
        """
        try:
            file_path = self._get_full_path(file_id)
            
            if not os.path.exists(file_path):
                current_app.logger.warning(f"尝试删除不存在的文件: {file_path}")
                return False
            
            os.remove(file_path)
            current_app.logger.info(f"文件删除成功: {file_path}")
            return True
            
        except Exception as e:
            current_app.logger.error(f"文件删除失败: {str(e)}")
            raise StorageError(f"文件删除失败: {str(e)}")
    
    def exists(self, file_id: str) -> bool:
        """
        检查文件是否存在
        
        Args:
            file_id: 文件名或相对路径
            
        Returns:
            bool: 文件是否存在
        """
        file_path = self._get_full_path(file_id)
        return os.path.exists(file_path)
    
    def get_url(self, file_id: str, expires_in: int = 3600) -> str:
        """
        获取文件访问URL
        
        Args:
            file_id: 文件名或相对路径
            expires_in: URL过期时间（秒，本地存储忽略）
            
        Returns:
            str: 文件访问URL
        """
        try:
            # 对于本地存储，使用Flask的静态文件路由
            # 假设文件存储在static/uploads目录下
            relative_path = os.path.relpath(self._get_full_path(file_id), current_app.root_path)
            return url_for('static', filename=relative_path, _external=True)
        except Exception:
            # 如果URL生成失败，返回文件路径
            return f"/static/uploads/{file_id}"
    
    def get_metadata(self, file_id: str) -> Dict[str, Any]:
        """
        获取文件元数据
        
        Args:
            file_id: 文件名或相对路径
            
        Returns:
            dict: 文件元数据
        """
        try:
            file_path = self._get_full_path(file_id)
            
            if not os.path.exists(file_path):
                raise StorageError(f"文件不存在: {file_id}")
            
            stat_info = os.stat(file_path)
            
            return {
                'file_id': file_id,
                'file_path': file_path,
                'size': stat_info.st_size,
                'created_at': datetime.fromtimestamp(stat_info.st_ctime).isoformat(),
                'modified_at': datetime.fromtimestamp(stat_info.st_mtime).isoformat(),
                'accessed_at': datetime.fromtimestamp(stat_info.st_atime).isoformat(),
            }
            
        except Exception as e:
            current_app.logger.error(f"获取文件元数据失败: {str(e)}")
            raise StorageError(f"获取文件元数据失败: {str(e)}")
    
    def list_files(self, prefix: str = '', limit: int = 100) -> List[Dict[str, Any]]:
        """
        列出文件
        
        Args:
            prefix: 文件前缀过滤
            limit: 返回数量限制
            
        Returns:
            list: 文件信息列表
        """
        try:
            files = []
            search_path = self.base_path
            
            if prefix:
                search_path = os.path.join(self.base_path, prefix)
            
            if not os.path.exists(search_path):
                return []
            
            count = 0
            for root, dirs, filenames in os.walk(search_path):
                for filename in filenames:
                    if count >= limit:
                        break
                    
                    file_path = os.path.join(root, filename)
                    relative_path = os.path.relpath(file_path, self.base_path)
                    
                    stat_info = os.stat(file_path)
                    
                    files.append({
                        'file_id': relative_path,
                        'filename': filename,
                        'file_path': file_path,
                        'size': stat_info.st_size,
                        'created_at': datetime.fromtimestamp(stat_info.st_ctime).isoformat(),
                        'modified_at': datetime.fromtimestamp(stat_info.st_mtime).isoformat(),
                    })
                    
                    count += 1
                
                if count >= limit:
                    break
            
            return files
            
        except Exception as e:
            current_app.logger.error(f"列出文件失败: {str(e)}")
            raise StorageError(f"列出文件失败: {str(e)}")
    
    def _get_full_path(self, file_id: str) -> str:
        """
        获取文件的完整路径
        
        Args:
            file_id: 文件名或相对路径
            
        Returns:
            str: 完整文件路径
        """
        # 如果file_id已经是绝对路径，直接返回
        if os.path.isabs(file_id):
            return file_id
        
        # 否则相对于基础路径
        return os.path.join(self.base_path, file_id)
    
    def get_disk_usage(self) -> Dict[str, Any]:
        """
        获取磁盘使用情况
        
        Returns:
            dict: 磁盘使用信息
        """
        try:
            total_size = 0
            file_count = 0
            
            for root, dirs, files in os.walk(self.base_path):
                for file in files:
                    file_path = os.path.join(root, file)
                    try:
                        total_size += os.path.getsize(file_path)
                        file_count += 1
                    except OSError:
                        continue
            
            return {
                'total_size': total_size,
                'file_count': file_count,
                'base_path': self.base_path,
                'free_space': shutil.disk_usage(self.base_path).free
            }
            
        except Exception as e:
            current_app.logger.error(f"获取磁盘使用情况失败: {str(e)}")
            return {
                'total_size': 0,
                'file_count': 0,
                'base_path': self.base_path,
                'free_space': 0,
                'error': str(e)
            }
    
    def cleanup_temp_files(self, older_than_hours: int = 24):
        """
        清理临时文件
        
        Args:
            older_than_hours: 清理多少小时前的临时文件
        """
        try:
            temp_dir = os.path.join(self.base_path, 'temp')
            if not os.path.exists(temp_dir):
                return
            
            current_time = datetime.now().timestamp()
            cutoff_time = current_time - (older_than_hours * 3600)
            
            deleted_count = 0
            for filename in os.listdir(temp_dir):
                file_path = os.path.join(temp_dir, filename)
                try:
                    if os.path.getmtime(file_path) < cutoff_time:
                        os.remove(file_path)
                        deleted_count += 1
                except OSError:
                    continue
            
            current_app.logger.info(f"清理了 {deleted_count} 个临时文件")
            
        except Exception as e:
            current_app.logger.error(f"清理临时文件失败: {str(e)}")
    
    def create_symlink(self, source_file_id: str, link_name: str) -> bool:
        """
        创建符号链接
        
        Args:
            source_file_id: 源文件ID
            link_name: 链接名称
            
        Returns:
            bool: 是否创建成功
        """
        try:
            source_path = self._get_full_path(source_file_id)
            link_path = self._get_full_path(link_name)
            
            if not os.path.exists(source_path):
                raise StorageError(f"源文件不存在: {source_file_id}")
            
            os.symlink(source_path, link_path)
            return True
            
        except Exception as e:
            current_app.logger.error(f"创建符号链接失败: {str(e)}")
            raise StorageError(f"创建符号链接失败: {str(e)}")